﻿using System;
using java = biz.ritter.javapi.lang;
using biz.ritter.javapi.lang;
using biz.ritter.javapi.lang.reflect;
using biz.ritter.javapi.util;
using Nofs.Net.Common.Interfaces.Domain;
using Nofs.Net.nofs.metadata.interfaces;

namespace Nofs.Net.Cache.Impl
{
    public class SerializerBuilder : ITranslatorStrategy
    {
        private IRepresentationBuilder _builder;
        private IMethodFilter _filter;

        public SerializerBuilder(IRepresentationBuilder builder, IMethodFilter methodFilter)
        {
            _builder = builder;
            _filter = methodFilter;
        }

        //@Override
        public void DeserializeInto(String data, IFileObject obj) //throws Exception 
        {
            Object value = obj.GetValue();
            _builder.PopulateWith(data);
            FromData(_builder.FindChildByName(_builder.GetRoot(), value.getClass().getName()), value);
        }

        //@Override
        public String Serialize(IFileObject obj) //throws Exception 
        {
            Object value = obj.GetValue();
            ToData(_builder.GetRoot(), value, value.getClass().getName());
            return _builder.TranslateToString();
        }

        //@SuppressWarnings("unchecked")
        private void ToData(IFolderReference parent, Object obj, String name) //throws Exception  
        {
            IFolderReference element = _builder.AddFolder(parent, name);

            foreach (Method method in obj.getClass().getDeclaredMethods())
            {
                if (_filter.UseMethod(obj, method)
                    && method.getName().startsWith("get")
                    && method.getName().length() > 3
                    && method.getParameterTypes().Length == 0)
                {
                    Object returnValue = method.invoke(obj);
                    String methodName = method.getName().substring(3);
                    if (returnValue == null)
                    {
                        _builder.AddFolder(element, methodName);
                    }
                    else if (isPrimitive(returnValue))
                    {
                        IFolderReference child = _builder.AddFolder(element, methodName);
                        _builder.SetFolderValue(child, returnValue.toString());
                    }
                    else if (isCollection(returnValue))
                    {
                        Collection<Object> collection = (Collection<Object>)returnValue;
                        IFolderReference child = _builder.AddFolder(element, methodName);
                        foreach (Object childObj in collection.toArray())
                        {
                            ToData(child, childObj, childObj.getClass().getName());
                        }
                    }
                    else
                    {
                        ToData(element, returnValue, methodName);
                    }
                }
            }
        }

        private void FromData(IFolderReference node, Object obj) //throws Exception 
        {
            foreach (Method method in obj.getClass().getDeclaredMethods())
            {
                if (_filter.UseMethod(obj, method)
                    && method.getName().startsWith("set")
                    && method.getName().length() > 3
                    && method.getParameterTypes().Length == 1)
                {
                    String methodName = method.getName().substring(3);
                    IFolderReference targetNode = _builder.FindChildByName(node, methodName);
                    if (method.getParameterTypes().Length == 1 && isPrimitive(method.getParameterTypes()[0]))
                    {
                        method.invoke(obj, primitiveFromString(_builder.GetFolderValue(targetNode), method.getParameterTypes()[0]));
                    }
                    else if (method.getParameterTypes().Length == 1 && isCollection(method.getParameterTypes()[0]))
                    {
                        ArrayList<Object> collection = new ArrayList<Object>();
                        FromCollection(targetNode, collection);
                        method.invoke(obj, collection);
                    }
                    else
                    {
                        Object child = constructObjectFromClassName(method.getName().substring(3));
                        FromData(targetNode, child);
                        method.invoke(obj, child);
                    }
                }
            }
        }

        private void FromCollection(IFolderReference node, Collection<Object> collection)// throws Exception 
        {
            foreach (IFolderReference childElement in _builder.GetChildren(node))
            {
                Object childObject = constructObjectFromClassName(childElement.getName());
                collection.add(childObject);
                FromData(childElement, childObject);
            }
        }

        //@SuppressWarnings("unchecked")
        private static bool isCollection(Object obj)
        {
            return obj is AbstractCollection<Object> ||
                   obj is AbstractList<Object>;
        }

        private static bool isCollection(Class c)
        {
            return
                c.isAssignableFrom(typeof(AbstractCollection<object>).getClass()) ||
                c.isAssignableFrom(typeof(AbstractList<Object>).getClass());
        }

        private static bool isPrimitive(Class c)
        {
            return
                c.GetType().IsPrimitive ||
                c.isAssignableFrom(typeof(String).getClass()) ||
                c.isAssignableFrom(typeof(java.Integer).getClass()) ||
                c.isAssignableFrom(typeof(Date).getClass()) ||
                c.isAssignableFrom(typeof(java.Float).getClass()) ||
                c.isAssignableFrom(typeof(java.Double).getClass()) ||
                c.isAssignableFrom(typeof(java.Long).getClass());
        }

        private static object primitiveFromString(String value, Class c) //throws Exception 
        {
            if (c.isAssignableFrom(typeof(String).getClass()))
            {
                return value;
            }
            else if (c.isAssignableFrom(typeof(int).getClass()))
            {
                return int.Parse(value);
            }
            else if (c.isAssignableFrom(typeof(float).getClass()))
            {
                return float.Parse(value);
            }
            else if (c.isAssignableFrom(typeof(double).getClass()))
            {
                return double.Parse(value);
            }
            else if (c.isAssignableFrom(typeof(long).getClass()))
            {
                return long.Parse(value);
            }
            else if (c.isAssignableFrom(typeof(java.Integer).getClass()))
            {
                return new java.Integer(value);
            }
            else if (c.isAssignableFrom(typeof(java.Float).getClass()))
            {
                return new java.Float(value);
            }
            else if (c.isAssignableFrom(typeof(java.Double).getClass()))
            {
                return new java.Double(value);
            }
            else if (c.isAssignableFrom(typeof(java.Long).getClass()))
            {
                return new java.Long(value);
            }
            else
            {
                throw new java.Exception("unknown primitive type: " + c.getName());
            }
        }

        private static Type getTypeFromName(String className)
        {
            return Type.GetType(className);
        }

        private static Class getClassFromName(String className) //throws ClassNotFoundException 
        {
            return Type.GetType(className).getClass();
        }

        private static Object constructObjectFromClassName(String className) //throws Exception 
        {
            System.Reflection.ConstructorInfo constructor = null;
            Type c = getTypeFromName(className);

            foreach (System.Reflection.ConstructorInfo cons in c.GetConstructors())
            {
                if (cons.GetParameters().Length == 0)
                {
                    constructor = cons;
                    break;
                }
            }

            if (constructor == null)
            {
                throw new java.Exception("could not find default constructor for " + c.GetType().Name);
            }
            return constructor.getClass().newInstance();
        }

        private static bool isPrimitive(Object obj)
        {
            return
                obj.GetType().IsPrimitive ||
                obj is String ||
                obj is java.Integer ||
                obj is Date ||
                obj is java.Float ||
                obj is java.Double ||
                obj is java.Long;
        }
    }

}
